package com.liuc.practice.ipersistent.sqlSession.impl;

import com.liuc.practice.ipersistent.pojo.Configuration;
import com.liuc.practice.ipersistent.pojo.SqlMappedStatement;
import com.liuc.practice.ipersistent.sqlSession.SqlSession;

import java.lang.reflect.*;
import java.util.List;

/**
 *
 * @Author Liu Chang
 * @Date 2020/12/13 6:50 上午
 */
public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws Exception {
        return  (List<E>) execute(statementId, params);
    }

    @Override
    public <E> E selectOne(String statementId, Object... params) throws Exception {
        List<E> objects = selectList(statementId, params);
        if (objects.size() > 0) {
            if (objects.size() > 1) {
                throw new Exception("符合条件查询结果 > 1");
            }
            return objects.get(0);
        }
        return null;
    }

    @Override
    public void insert(String statementId, Object... entity) throws Exception {
        execute(statementId, entity);
    }


    @Override
    public void update(String statementId, Object... entity) throws Exception {
        execute(statementId, entity);
    }

    @Override
    public void delete(String statementId, Object... params) throws Exception {
        execute(statementId, params);
    }

    /**
     * 目的：为了解决代码重复和 statementId 硬编码的问题
     * 解决方式：使用 JDK 动态代理生成 mapper 接口代理实现类的代理对象，并返回
     * 动态代理：多个不同的类中功能相同的函数进行统一处理，而不用修改每个代理类中的方法
     *
     * 备注：代理对象调用接口中的任意方法，都会执行该方法中的 invoke(proxy, method, args) 方法
     * @param clazz
     * @param <E>
     * @return
     */
    @Override
    public <E> E getMapper(Class<?> clazz) {
        Object proxyObject = Proxy.newProxyInstance(
                DefaultSqlSession.class.getClassLoader(),
                new Class[]{clazz},
                new InvocationHandler() {
                    /**
                     * 1. 将 statementId 硬编码转为动态拼接
                     * 通过 Method 可以获取到该方法的 className 和 methodName
                     * 由于 invoke 方法无法获取到 xml 中的 namespace 和 id
                     * 所以需要 xml 文件中的 nameSpace、id 需要和 className、methodName 保持一致
                     * 保证根据 statementId 获取到 mappedStatementMap 中的 sqlMappedStatement;
                     *
                     * 2. 执行接口实际调用时应该调用的方法
                     * 调用增删改查方法时，通用两个参数：statementId 和 Object... params
                     *
                     * @param proxy 当前代理对象的引用
                     * @param method 代理对象调用接口方法的引用
                     * @param args 调用 method 方法时的传参
                     * @return
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        String methodName = method.getName();
                        // 执行获取到的：com.liuc.practice.ipersistent.test.IUserMapper
                        String declaringClassName = method.getDeclaringClass().getName();
                        // 执行获取到的：java.lang.reflect.Method
                        String className = method.getClass().getName();
                        String statementId = declaringClassName + "." + methodName;

                        Type genericReturnType = method.getGenericReturnType();
                        // 只有查询方法有返回值，所以根据返回值来进行判断执行哪个方法
                        if (genericReturnType.getTypeName() != "void") {
                            // 判断是否进行了泛型类型参数化
                            if(genericReturnType instanceof ParameterizedType){
                                List<Object> objects = selectList(statementId, args);
                                return objects;
                            }

                            return selectOne(statementId,args);
                        }

                        // insert/update/delete 方法内容都一样，抽象成 execute 方法即可
                        return execute(statementId, args);
                    }
                });

        return (E) proxyObject;
    }

    private List<Object> execute(String statementId, Object... params) throws Exception {
        // 根据每个 select 的唯一表示 statementId 找到对应的 sql 语句
        SqlMappedStatement statement = configuration.getSqlMappedStatements().get(statementId);

        // 委托给 Executor 执行具体的 sql 语句
        SampleExecutor executor = new SampleExecutor(configuration);
        return executor.query(statement, params);
    }
}
